<!DOCTYPE html>
<html><head>
  <script src="../lightgl.js"></script>
  <script src="csg.min.js"></script>
  <style>

body {
  font: 14px/20px 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, Arial, sans-serif;
  max-width: 800px;
  margin: 0 auto;
  padding: 50px 50px 200px 50px;
}

pre, code {
  font: 12px/20px Monaco, monospace;
  border: 1px solid #CCC;
  border-radius: 3px;
  background: #F9F9F9;
  padding: 0 3px;
  color: #555;
}
pre { padding: 10px; }

h1, h2 { font: bold 50px/50px 'Helvetica Neue', Helvetica, Arial; }
h2 { font-size: 30px; margin: 100px 0 0 0; }
a { color: inherit; }
.viewer { width: 200px; height: 200px; background: #EEE; }
#combined .viewer { width: 150px; height: 150px; }
table { border-collapse: collapse; margin: 0 auto; }
td { padding: 5px; text-align: center; }
td code { background: none; border: none; color: inherit; }
canvas { cursor: move; }

  </style>
</head><body>
  <h1>csg.js</h1>
  <p><b>Source code:</b> <a href="http://github.com/evanw/csg.js/">http://github.com/evanw/csg.js/</a><br>
    <b>Documentation:</b> <a href="http://evanw.github.com/csg.js/docs/">http://evanw.github.com/csg.js/docs/</a></p>

  <h2>Introduction</h2>
  <p>Constructive Solid Geometry (CSG) is a modeling technique that uses Boolean operations like union and intersection to combine 3D solids. This library implements CSG operations on meshes elegantly and concisely using BSP trees, and is meant to serve as an easily understandable implementation of the algorithm. All edge cases involving overlapping coplanar polygons in both solids are correctly handled.</p>
  <p>Example usage:</p>
  <pre>var cube = CSG.cube();
var sphere = CSG.sphere({ radius: 1.3 });
var polygons = cube.subtract(sphere).toPolygons();</pre>

  <h2>Operations</h2>
  <p>This library provides three CSG operations: union, subtract, and intersect. The operations are rendered below using <a href="http://github.com/evanw/lightgl.js/">lightgl.js</a> and can be rotated by dragging the mouse.</p>
  <table>
    <tr>
      <td><div id="0" class="viewer"></div><code>a</code></td>
      <td><div id="1" class="viewer"></div><code>b</code></td>
    </tr>
  </table>
  <table>
    <tr>
      <td><div id="2" class="viewer"></div><code>a.union(b)</code></td>
      <td><div id="3" class="viewer"></div><code>a.subtract(b)</code></td>
      <td><div id="4" class="viewer"></div><code>a.intersect(b)</code></td>
    </tr>
  </table>
  <p>The solids <code>a</code> and <code>b</code> above were generated with the following code:</p>
  <pre>var a = CSG.cube({ center: [-0.25, -0.25, -0.25] });
var b = CSG.sphere({ radius: 1.3, center: [0.25, 0.25, 0.25] });</pre>

  <h2>Combined CSG Example</h2>
  <p>Below is a solid constructed from a combination of operations:</p>
  <table id="combined">
    <tr>
      <td><div id="5" class="viewer"></div><code>a</code></td>
      <td><div id="6" class="viewer"></div><code>b</code></td>
      <td><div id="7" class="viewer"></div><code>c</code></td>
      <td><div id="8" class="viewer"></div><code>d</code></td>
      <td><div id="9" class="viewer"></div><code>e</code></td>
    </tr>
  </table>
  <table>
    <tr>
      <td><div id="10" class="viewer" style="width:400px;height:400px;"></div><code>a.intersect(b).subtract(c.union(d).union(e))</code></td>
    </tr>
  </table>
  <p>The solids above were generated with the following code:</p>
  <pre>var a = CSG.cube();
var b = CSG.sphere({ radius: 1.35, stacks: 12 });
var c = CSG.cylinder({ radius: 0.7, start: [-1, 0, 0], end: [1, 0, 0] });
var d = CSG.cylinder({ radius: 0.7, start: [0, -1, 0], end: [0, 1, 0] });
var e = CSG.cylinder({ radius: 0.7, start: [0, 0, -1], end: [0, 0, 1] });</pre>

  <script>

// Set the color of all polygons in this solid
CSG.prototype.setColor = function(r, g, b) {
  this.toPolygons().map(function(polygon) {
    polygon.shared = [r, g, b];
  });
};

// Convert from CSG solid to GL.Mesh object
CSG.prototype.toMesh = function() {
  var mesh = new GL.Mesh({ normals: true, colors: true });
  var indexer = new GL.Indexer();
  this.toPolygons().map(function(polygon) {
    var indices = polygon.vertices.map(function(vertex) {
      vertex.color = polygon.shared;
      return indexer.add(vertex);
    });
    for (var i = 2; i < indices.length; i++) {
      mesh.triangles.push([indices[0], indices[i - 1], indices[i]]);
    }
  });
  mesh.vertices = indexer.unique.map(function(v) { return [v.pos.x, v.pos.y, v.pos.z]; });
  mesh.normals = indexer.unique.map(function(v) { return [v.normal.x, v.normal.y, v.normal.z]; });
  mesh.colors = indexer.unique.map(function(v) { return v.color; });
  mesh.computeWireframe();
  return mesh;
};

var angleX = 20;
var angleY = 20;
var viewers = [];

// A viewer is a WebGL canvas that lets the user view a mesh. The user can
// tumble it around by dragging the mouse.
function Viewer(csg, width, height, depth) {
  viewers.push(this);

  // Get a new WebGL canvas
  var gl = GL.create();
  this.gl = gl;
  this.mesh = csg.toMesh();

  // Set up the viewport
  gl.canvas.width = width;
  gl.canvas.height = height;
  gl.viewport(0, 0, width, height);
  gl.matrixMode(gl.PROJECTION);
  gl.loadIdentity();
  gl.perspective(45, width / height, 0.1, 100);
  gl.matrixMode(gl.MODELVIEW);

  // Set up WebGL state
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  gl.clearColor(0.93, 0.93, 0.93, 1);
  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.CULL_FACE);
  gl.polygonOffset(1, 1);

  // Black shader for wireframe
  this.blackShader = new GL.Shader('\
    void main() {\
      gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
    }\
  ', '\
    void main() {\
      gl_FragColor = vec4(0.0, 0.0, 0.0, 0.3);\
    }\
  ');

  // Shader with diffuse and specular lighting
  this.lightingShader = new GL.Shader('\
    varying vec3 color;\
    varying vec3 normal;\
    varying vec3 light;\
    void main() {\
      const vec3 lightDir = vec3(1.0, 2.0, 3.0) / 3.741657386773941;\
      light = (gl_ModelViewMatrix * vec4(lightDir, 0.0)).xyz;\
      color = gl_Color.rgb;\
      normal = gl_NormalMatrix * gl_Normal;\
      gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
    }\
  ', '\
    varying vec3 color;\
    varying vec3 normal;\
    varying vec3 light;\
    void main() {\
      vec3 n = normalize(normal);\
      float diffuse = max(0.0, dot(light, n));\
      float specular = pow(max(0.0, -reflect(light, n).z), 32.0) * sqrt(diffuse);\
      gl_FragColor = vec4(mix(color * (0.3 + 0.7 * diffuse), vec3(1.0), specular), 1.0);\
    }\
  ');

  gl.onmousemove = function(e) {
    if (e.dragging) {
      angleY += e.deltaX * 2;
      angleX += e.deltaY * 2;
      angleX = Math.max(-90, Math.min(90, angleX));

      viewers.map(function(viewer) {
        viewer.gl.ondraw();
      });
    }
  };

  var that = this;
  gl.ondraw = function() {
    gl.makeCurrent();

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.loadIdentity();
    gl.translate(0, 0, -depth);
    gl.rotate(angleX, 1, 0, 0);
    gl.rotate(angleY, 0, 1, 0);

    gl.enable(gl.POLYGON_OFFSET_FILL);
    that.lightingShader.draw(that.mesh, gl.TRIANGLES);
    gl.disable(gl.POLYGON_OFFSET_FILL);

    gl.enable(gl.BLEND);
    that.blackShader.draw(that.mesh, gl.LINES);
    gl.disable(gl.BLEND);
  };

  gl.ondraw();
}

var nextID = 0;
function addViewer(viewer) {
  document.getElementById(nextID++).appendChild(viewer.gl.canvas);
}

// Test simple cases
var a = CSG.cube({ center: [-0.25, -0.25, -0.25] });
var b = CSG.sphere({ radius: 1.3, center: [0.25, 0.25, 0.25] });
a.setColor(1, 0, 0);
b.setColor(0, 0, 1);
var operations = [
  a,
  b,
  a.union(b),
  a.subtract(b),
  a.intersect(b)
];
for (var i = 0; i < operations.length; i++) {
  addViewer(new Viewer(operations[i], 200, 200, 6));
}

// Generate example from wikipedia
var a = CSG.cube();
var b = CSG.sphere({ radius: 1.35, stacks: 12 });
var c = CSG.cylinder({ radius: 0.7, start: [-1, 0, 0], end: [1, 0, 0] });
var d = CSG.cylinder({ radius: 0.7, start: [0, -1, 0], end: [0, 1, 0] });
var e = CSG.cylinder({ radius: 0.7, start: [0, 0, -1], end: [0, 0, 1] });
a.setColor(1, 0, 0);
b.setColor(0, 0, 1);
c.setColor(0, 1, 0);
d.setColor(0, 1, 0);
e.setColor(0, 1, 0);
var operations = [
  a,
  b,
  c,
  d,
  e,
  a.intersect(b).subtract(c.union(d).union(e))
];
for (var i = 0; i < operations.length; i++) {
  var size = i + 1 == operations.length ? 400 : 150;
  addViewer(new Viewer(operations[i], size, size, 5));
}

  </script>
</body></html>
